// Maciej Chałapuk (maciej@chalapuk.pl)
// vim: ts=2 sw=2 expandtab

#ifndef BITSET_HPP
#define BITSET_HPP


#include "static_for.hpp"
#include "bitcount.hpp"

#include <cstdint>
#include <cassert>
#include <iostream>

namespace experiment {


template <
  size_t size_,
  class bitcount_ = bitcount::parallel
>
class bitset {
public:
  template <class word_type>
  class ref {
  public:
    ref& operator=(bool value) {
      if (value) {
        m_word |= mask();
      } else {
        m_word &= ~mask();
      }
    }

    operator bool() const {
      return (m_word & mask());
    }

  private:
    typedef typename std::make_unsigned<word_type>::type uword_type;

    word_type & m_word;
    size_t m_pos;

    ref(word_type & word, size_t pos)
        : m_word(word), m_pos(pos) {
    }
    uword_type mask() const {
      return (((static_cast<uword_type>(-1) >> 1) + 1u) >> m_pos);
    }
    friend class bitset;
  };


  typedef ref<intptr_t> reference;
  typedef ref<const intptr_t> const_reference;

  bitset() {
  }

  template <class Iterator>
  bitset(Iterator begin, Iterator end) {
    typedef typename std::remove_reference<decltype(*begin)>::type src_type;
    src_type *dst = reinterpret_cast<src_type*>(m_bytes);
    for (Iterator it = begin; it!=end; ++it, ++dst) {
      *dst = *it;
    }
  }

  reference operator[](size_t index) {
    assert(index < size_);
    return reference(m_bytes[index/c_bitsPerElem], index%c_bitsPerElem);
  }
  const_reference operator[](size_t index) const {
    assert(index < size_);
    return const_reference(m_bytes[index/c_bitsPerElem], index%c_bitsPerElem);
  }

  // not inline because of long machine code
  bitset& operator&=(bitset const& rhs);
  bitset& reset();
  bitset& flip();
  size_t count() const;

private:
  typedef bitcount_ bitcount;

  static constexpr size_t c_bitsPerByte = 8;
  static constexpr size_t c_bitsPerElem = sizeof(intptr_t) * c_bitsPerByte;
  static constexpr size_t c_arraySize =
    size_/c_bitsPerElem + (size_%c_bitsPerElem?1: 0);
  intptr_t m_bytes[c_arraySize];

  template <size_t size, class bitcount>
  friend std::ostream& operator<<(std::ostream&, bitset<size, bitcount> const&);
};


template <size_t size, class bitcount>
bitset<size, bitcount>& bitset<size, bitcount>::operator&=(
    bitset<size, bitcount> const& rhs) {
  intptr_t const* src = rhs.m_bytes;
  intptr_t * dst = m_bytes;
  static_for<c_arraySize>([&src, &dst](size_t) { *dst++ &= *src++; });
}

template <size_t size, class bitcount>
bitset<size, bitcount>& bitset<size, bitcount>::reset() {
  intptr_t *dst = m_bytes;
  static_for<c_arraySize>([&dst](size_t) { *dst++ = 0; });
}

template <size_t size, class bitcount>
bitset<size, bitcount>& bitset<size, bitcount>::flip() {
  intptr_t *dst = m_bytes;
  static_for<c_arraySize>([&dst](size_t) { *dst++ = ~*dst; });
}

template <size_t size, class bitcount>
size_t bitset<size, bitcount>::count() const {
  intptr_t const* src = m_bytes;
  size_t count = 0;
  static_for<c_arraySize>([&src, &count](size_t) {
      static const bitcount counter = {};
      count += counter(*src++);
      });
  return count;
}

template <size_t size, class bitcount>
std::ostream& operator<<(std::ostream& out,
    bitset<size, bitcount> const& bitset) {
  static_for<size>([&](size_t i) { out << bitset[i]; });
}


} // namespace experiment

#endif // include guard

